<template>
	<div class="bear-ImagePreview my-unselect"
		 v-show="visible"
		 @wheel.prevent="$_bear_wheelFn"
		 @mousemove="$_bear_mousemoveFn"
		 @mouseup="$_bear_mouseupFn">
		<i class="bear-icon el-icon-close" @click="close"></i>
		<div class="bear-toolbar">
			<i class="bear-icon el-icon-full-screen" @click="reset"></i>
			<i class="bear-icon el-icon-arrow-left" @click="previous"></i>
			<i class="bear-icon el-icon-arrow-right" @click="next"></i>
			<i class="bear-icon el-icon-refresh-left" @click="rotateLeft"></i>
			<i class="bear-icon el-icon-refresh-right" @click="rotateRight"></i>
		</div>
		<div class="bear-head">
			<div>{{position+1}}/{{imgObjs.length}}</div>
			<div>{{imgObjs[position]|showIfNotNull('name')}}</div>
		</div>
		<div class="bear-container"
			 :style="{transform:'translateX('+translateX+'px) translateY('+translateY+'px)'}">
			<img draggable="false"
				 :src="imgObjs[position]|showIfNotNull('url')"
				 :style="{transform:'scale('+scale+') rotate('+rotate+'deg)',transition:transition}"
				 @dragstart.prevent
				 @mousedown="$_bear_mousedownFn">
		</div>
	</div>
</template>

<script>
	/**
	 * @author 唐国雄
	 * 图片预览
	 * 属性----------------------
	 * v-model			是否可见
	 * :url-attr		显示的url属性名
	 * :name-attr		名称的属性名
	 * :images			需要预览的图片
	 * :start-position	预览起始索引
	 * :max-zoom		最大缩放比例
	 * :min-zoom		最小缩放比例
	 * 事件----------------------
	 * @close			关闭预览
	 * @change			切换图片
	 * @scale			缩放图片
	 * 方法----------------------
	 * to(index)			指定切换
	 * offset(offset)		相对切换
	 * previous()			上一张图片
	 * next()				下一张图片
	 * close()				关闭预览
	 * reset()				重置当前图片
	 * rotateOffset(offset)	相对旋转
	 * rotateLeft()			逆时针旋转
	 * rotateRight()		顺时针旋转
	 *
	 * @typedef ImagePreview
	 * @event ImagePreview#close
	 * @type {Object} 数据
	 * @property {Number} index 当前预览索引
	 * @property {String} url	当前预览url
	 * @event ImagePreview#change
	 * @type {Number} 当前预览索引
	 * @event ImagePreview#scale
	 * @type {Object} 数据
	 * @property {Number} index 当前预览索引
	 * @property {Number} scale 当前缩放比例
	 */
	import Vue from 'vue';
	const ImagePreview={
		name:'ImagePreview',
		filters:{
			showIfNotNull(obj,attr){
				if(obj){
					return obj[attr];
				}
				return '';
			}
		},
		props:{
			urlAttr:{
				type:String,
				default:'url'
			},
			nameAttr:{
				type:String,
				default:'name'
			},
			/**
			 * 需要预览的图片URL
			 * @type {String|String[]|Object[]}
			 */
			images:[Array,String],
			// 预览起始索引
			startPosition:{
				type:Number,
				default:0
			},
			// 最大缩放比例
			maxZoom:{
				type:Number,
				default:5
			},
			// 最小缩放比例
			minZoom:{
				type:Number,
				default:0.2
			},
			// 初始化是否可见
			value:Boolean
		},
		data(){
			return {
				// 当前显示图片的索引
				position:this.startPosition,
				// 是否可见
				visible:this.value,
				// 旋转角度
				rotate:0,
				// 缩放比例
				scale:1,
				// 水平移动
				translateX:0,
				// 垂直移动
				translateY:0,
				// 过渡效果
				transition:'',
				// 正在移动
				moving:false
			};
		},
		computed:{
			imgObjs(){
				const images=this.images;
				const urlAttr=this.urlAttr;
				const nameAttr=this.nameAttr;
				if(typeof images==='string'){
					if(images){
						return [{url:images}];
					}
				}else if(Array.isArray(images)){
					if(images.length){
						return images.map(function(ele){
							if(typeof ele==='string'){
								return {url:ele};
							}else{
								return {url:ele[urlAttr],name:ele[nameAttr]};
							}
						});
					}
				}
				return [];
			}
		},
		watch:{
			value(val){
				this.visible=val;
			},
			startPosition(val){
				this.position=val;
			}
		},
		methods:{
			$_bear_mousedownFn(e){
				this.moving=true;
				this.lastX=e.clientX;
				this.lastY=e.clientY;
			},
			$_bear_mouseupFn(){
				this.moving=false;
			},
			$_bear_mousemoveFn(e){
				if(this.moving){
					const clientX=e.clientX;
					const clientY=e.clientY;
					this.translateX+=clientX-this.lastX;
					this.translateY+=clientY-this.lastY;
					this.lastX=clientX;
					this.lastY=clientY;
				}
			},
			// 缩放
			$_bear_wheelFn(e){
				let scale=e.deltaY<0?1.1:0.9;
				scale*=this.scale;
				if(scale>=this.minZoom&&scale<=this.maxZoom){
					this.scale=scale;
					this.$emit('scale',{
						index:this.position,
						scale:scale
					});
				}
			},
			// 指定切换
			to(index){
				this.position=index%this.imgObjs.length||0;
				this.reset();
				this.$emit('change',this.position);
			},
			// 相对切换
			offset(offset){
				this.to(this.position+offset+this.imgObjs.length);
			},
			// 上一张图片
			previous(){
				this.offset(-1);
			},
			// 下一张图片
			next(){
				this.offset(1);
			},
			// 关闭预览
			close(){
				this.visible=false;
				this.reset();
				this.$emit('input',false);
				const index=this.position;
				const imgObj=this.imgObjs[index];
				this.$emit('close',{
					index:index,
					url:imgObj&&imgObj.url
				});
			},
			// 重置当前图片
			reset(){
				this.transition='0s';// 关闭过渡
				this.scale=1;
				this.rotate=0;
				this.translateX=0;
				this.translateY=0;
			},
			// 相对旋转
			rotateOffset(offset){
				this.transition='';// 开启过渡
				this.rotate=this.rotate+offset;
			},
			// 逆时针旋转
			rotateLeft(){
				this.rotateOffset(-90);
			},
			// 顺时针旋转
			rotateRight(){
				this.rotateOffset(+90);
			}
		}
	};
	let instance;
	const defaultConfig={
		value:true,
		urlAttr:'url',
		nameAttr:'name',
		images:[],
		startPosition:0,
		maxZoom:5,
		minZoom:0.2,
		onChange:null,
		onScale:null
	};
	/**
	 * 选项对象
	 * @typedef PreviewOption
	 * @property {String} [urlAttr='url'] 显示的url属性名
	 * @property {String} [nameAttr='name'] 名称的属性名
	 * @property {String[]|Object[]} images 预览的图片
	 * @property {Number} [startPosition=0] 预览起始索引
	 * @property {Number} [maxZoom=5] 最大缩放比例
	 * @property {Number} [minZoom=0.2] 最小缩放比例
	 * @property {Function} onChange 切换图片的回调
	 * @property {Function} onScale 缩放图片的回调
	 * @property {Function} onClose 关闭预览的回调
	 */
	/**
	 * 图片预览API调用方式
	 * @param {String[]|Object[]|PreviewOption} images 预览的图片或者选项对象
	 * @param {Number} [startPosition=0] 预览起始索引
	 * @return {ImagePreview} ImagePreview的实例
	 */
	function ImagePreviewFn(images,startPosition=0){
		if(!instance){
			instance=new Vue(ImagePreview).$mount();
			top.document.body.appendChild(instance.$el);
			instance.$on('change',function(index){
				if(instance.onChange){
					instance.onChange(index);
				}
			});
			instance.$on('scale',function(data){
				if(instance.onScale){
					instance.onScale(data);
				}
			});
		}
		const options=Array.isArray(images)?{
			images:images,
			startPosition:startPosition
		}:images;
		Object.assign(instance,defaultConfig,options);
		instance.$once('close',function(data){
			instance.images=[];
			instance.value=false;
			instance.startPosition=data.index;
		});
		if(options.onClose){
			instance.$off('close');
			instance.$once('close',options.onClose);
		}
		return instance;
	}
	Vue.ImagePreviewFn=ImagePreviewFn;
	export {ImagePreviewFn};
	export default ImagePreview;
</script>

<style lang="scss">
	$but-size:44px;
	$toolbar-padding:23px;
	$toolbar-width:$but-size*5+$toolbar-padding*2;
	$head-width:500px;
	$but-font-size:24px;
	$mask-color:rgba(0,0,0,.5);
	$but-bg-color:#606266;
	$but-hover-color:#4a4a4a;
	.bear-ImagePreview{
		z-index:99999;
		position:fixed;
		top:0;
		left:0;
		width:100%;
		height:100%;
		background:$mask-color;
		font-size:$but-font-size;
		color:#fff;
		>.bear-head{
			z-index:10;
			opacity:.8;
			background:$but-bg-color;
			font-size:18px;
			position:absolute;
			width:$head-width;
			line-height:1.5;
			text-align:center;
			left:calc(50% - #{$head-width / 2});
			top:0;
			border-radius:0 0 5px 5px;
			>div:last-child{
				font-size:14px;
			}
		}
		>.bear-container{
			height:100%;
			width:100%;
			display:flex;
			align-items:center;
			justify-content:center;
			>img{
				transition:transform .3s;
				max-height:100%;
				max-width:100%;
			}
		}
		>.bear-hide{
			display:none;
		}
		>.bear-icon.el-icon-close{
			z-index:10;
			width:$but-size;
			line-height:$but-size;
			text-align:center;
			border-radius:50%;
			background:$but-bg-color;
			cursor:pointer;
			position:absolute;
			top:30px;
			right:30px;
			opacity:.8;
			&:hover{
				background:$but-hover-color;
			}
		}
		>.bear-toolbar{
			z-index:10;
			padding:0 $toolbar-padding;
			border-radius:$but-size/2;
			width:$toolbar-width;
			height:$but-size;
			background:$but-bg-color;
			position:absolute;
			bottom:15px;
			left:calc(50% - #{$toolbar-width / 2});
			opacity:.8;
			>.bear-icon{
				width:$but-size;
				line-height:$but-size;
				text-align:center;
				cursor:pointer;
				&:hover{
					background:$but-hover-color;
				}
			}
		}
	}
</style>
